package cn.yangliu.tools;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileMode;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.system.oshi.OshiUtil;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import oshi.software.os.OperatingSystem;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 问道于盲
 * @date 2021-09-27
 */
@Slf4j
public class AppHandler {

    /**
     * 工作目录
     */
    public static final String USER_DIR = System.getProperty("user.dir");

    /**
     * 串口虚拟工具存放名称
     */
    public static final String SERIAL_PORT_DRIVER = "Virtual_Serial_Port_Driver";

    /** 日志记录操作对象 sl4j日志门面 */
    private static final Logger LOGGER = LoggerFactory.getLogger(AppHandler.class);

    /**
     * 线程池
     */
    private static final ExecutorService POOL =  new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(), Executors.defaultThreadFactory());

    /**
     * 私有构造器
     */
    private AppHandler(){
    }

    /**
     * 退出是否执行
     */
    public static void exit(){
        OperatingSystem os = OshiUtil.getOs();
        if (Objects.equals(os.getFamily(), "Windows")){
            String cmd = "taskkill /im vspdconfig.exe /f";
            RuntimeUtil.exec(cmd);
        }
        System.exit(0);
        POOL.shutdown();
    }

    /**
     * 初始化执行
     * 1 解压串口虚拟化程序
     * 2 copy dll
     * 3 创建数据库文件夹
     * 4 更改id生成器逻辑
     */
    public static void initialize() {
        checkProcess();
        OperatingSystem os = OshiUtil.getOs();
        if (Objects.equals(os.getFamily(), "Windows")){
            unzipSerialPortDriver();
        }
        createDbDirectory();
        changeIdGeneratorLogic();
    }

    /**
     * 检查进程是否存在
     */
    private static void checkProcess(){
        File file = FileUtil.touch(USER_DIR, "process.lock");
        RandomAccessFile raf = FileUtil.createRandomAccessFile(file, FileMode.rwd);
        ScheduledThreadPoolExecutor scheduledExecutor = ThreadUtil.createScheduledExecutor(1);
        // 延迟释放资源
        scheduledExecutor.schedule(()->{
            try {
                raf.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }, 10, TimeUnit.SECONDS);
        try {
            //加锁 如果获取不到锁, 返回值为null, 程序进入自杀环节
            FileLock fileLock = raf.getChannel().tryLock();
            if (Objects.isNull(fileLock)) {
                killSelf();
            }
        } catch (Exception e){
            // 异常情况 程序也进入自杀环节
            LOGGER.warn(e.getMessage());
            killSelf();
        }
    }

    /**
     * 程序自杀
     */
    private static void killSelf(){
        OperatingSystem os = OshiUtil.getOs();

        // 获取当前进程
        int processId = os.getProcessId();

        try {
            String command = "";
            if (Objects.equals(os.getFamily(), "Windows")){
                command = String.format("taskkill /f /pid %s", processId);
            } else {
                command = String.format("kill -9  %s", processId);
            }
            //自杀
            Runtime.getRuntime().exec(command);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 解压虚拟化串口工具
     * 采用异步方式
     */
    private static void unzipSerialPortDriver() {
        POOL.submit(()->{
            String directory = USER_DIR + File.separator + SERIAL_PORT_DRIVER;
            if (new File(directory).exists()) {
                return;
            }
            Resource resource = new FileSystemResource(USER_DIR + File.separator + "ext" + File.separator + "Virtual_Serial_Port_Driver.zip");
            try (InputStream inputStream = resource.getInputStream()) {

                ZipUtil.unzip(inputStream, new File(USER_DIR + File.separator + "ext"), StandardCharsets.UTF_8);
            } catch (Exception e) {
                throw new UnsupportedOperationException(e);
            }
        });
    }

    /**
     * 创建数据库文件夹
     */
    private static void createDbDirectory() {
        File dbDir = new File(USER_DIR + File.separator + "db");
        if (!dbDir.exists()) {
            dbDir.mkdirs();
        }
    }

    /**
     * 更改id生成器逻辑
     */
    private static void changeIdGeneratorLogic() {
        try {
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
            CtClass cc = pool.get("org.hibernate.id.enhanced.SequenceStyleGenerator");
            CtMethod ctMethod = cc.getDeclaredMethod("generate",
                    new CtClass[]{pool.get("org.hibernate.engine.spi.SharedSessionContractImplementor"), pool.get(
                            "java.lang.Object")});
            String code = "        java.io.Serializable identifier = session.getEntityPersister(null, object)" + "           .getClassMetadata().getIdentifier(object, session);" + "        if (java.util.Objects.nonNull(identifier)) {" + "            return identifier;" + "        }";
            ctMethod.insertBefore(code);
            cc.toClass();
        } catch (Exception e) {
            throw new UnsupportedOperationException(e.getMessage(), e);
        }

    }

    public static void startUI(boolean devToolEnable, String server){
        try {

            Runtime rt = Runtime.getRuntime();
            List<String> cmds = new ArrayList<>();

            OperatingSystem os = OshiUtil.getOs();
            if (Objects.equals(os.getFamily(), "Windows")){
                cmds.add("cmd");
                cmds.add("/c");
                cmds.add("start");
                cmds.add("/b");
                String path = USER_DIR + File.separator + "ui" + File.separator + "serialport.exe";
                cmds.add(path);
                log.info("UI params is {}", path);
            } else {
                String path = "." + File.separator + "ui" + File.separator + "SerialportTool";
                cmds.add(path);
                log.info("UI params is {}", path);
                cmds.add("--no-sandbox");
            }

            cmds.add("--server=" + server);
            if (devToolEnable) {
                cmds.add("--devTool");
            }
            String[] targetCMD = cmds.toArray(new String[0]);
            log.info("UI cmd {}", cmds);
            Process p = rt.exec(targetCMD);
            p.waitFor();
            p.destroy();

        }catch (Exception e){
            throw new UnsupportedOperationException(e);
        }

    }

}
